package com.gitee.feizns.dynamic.bean;

import com.gitee.feizns.dynamic.reflect.Constructors;

import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;

/**
 * <b>实体类操作工具类</b>
 * @author feizns
 * @since 2019/6/9 0009
 */
public abstract class Beans {

    /**
     * <b>新创建一个对象并将源对象的属性复制到其中</b>
     * @see #copyProperties(Object, Object[])
     * @param targetType 目标对象类型
     * @param source 源对象列表
     * @param <T> 目标对象类型
     * @return 目标对象类型
     */
    public static <T> T copyProperties(Class<T> targetType, Object... source) {
        return copyProperties(Constructors.newInstance(targetType), source);
    }

    /**
     * <b>复制属性支持注解</b>
     * <p>将 sources 中的所有实体类中的属性都尝试的赋值给 target</p>
     * <p>例：假设有实体类 A{id:2}, B{name:"hello", name2:"world"}, C{id:0,name:null}, D{id:0,name:null}</p>
     * <div>
     *      <p><code>BeanUtils.copyProperties(c, new A(), new B());//id:2, name:hello<code/>是常规用法</p>
     *      <p><code>BeanUtils.copyProperties(d, new A(), new B());//id:2, name:world<code/>@TryGet将实体b.name2尝试获取到实体d中</p>
     * </div>
     * <pre>{@code
     *      public static void main(String[] args) {
     *         @Data
     *         class A { int id = 2; }
     *         @Data
     *         class B { String name = "hello"; String name2 = "world"; }
     *
     *         @Data
     *         class C { int id; String name; public String toString() { return String.format("id:%s, name:%s", id, name); } }
     *         C c = new C();
     *         BeanUtils.copyProperties(c, new A(), new B());
     *         System.out.println(c);      //id:2, name:hello
     *
     *         @Data
     *         class D { int id; @TryGet("name2") String name; public String toString() { return String.format("id:%s, name:%s", id, name); } }
     *         D d = new D();
     *         BeanUtils.copyProperties(d, new A(), new B());
     *         System.out.println(d);      //id:2, name:hello
     *      }
     * }</pre>
     * @see TryGet
     * @param target 复制到的目标对象
     * @param sources 源对象列表
     * @param <T> 目标对象类型
     * @return 期望复制到的target对象
     */
    @SafeVarargs
    public static <T, U> T copyProperties(T target, U... sources) {
        Props.ofWritable(target).forEach(prop -> java.util.Arrays.stream(sources).forEach(prop::tryGet));
        return target;
    }

    /**
     * <b>新创建一个对象并将源对象的属性复制到其中</b>
     * @see #copyProperties(Class, Object...)
     * @see #copyPropertiesTo(Object, BiFunction, Object[])
     * @param targetType 目标对象类型
     * @param source 源对象列表
     * @param <T> 目标对象类型
     * @return 目标对象类型
     */
    public static <T> T copyProperties(Class<T> targetType, Function<String, String> keyMap, Object... source) {
        return copyProperties(Constructors.newInstance(targetType), keyMap, source);
    }

    /**
     * <b>复制属性，并支持自定义从sources属性名映射到target属性名的映射规则</b>
     * <pre>{@code
     *     public static void main(String[] args) {
     *         @Data
     *         class A { int type_id = 1024; }
     *
     *         @Data
     *         class B { int typeId = 1; }
     *
     *         A a = new A();
     *         B b = new B();
     *
     *         //（将原先type_id的值映射到typeId）输出的结果为：B(typeId=1024)
     *         System.out.println(BeanUtils.copyProperties(b, StringUtils::underlineToHump, a));
     *         //B(typeId=1024)
     *         System.out.println(b);
     *     }
     * }</pre>
     * @param target 目标对象
     * @param keyMapping 自定义从sources属性名映射到target属性名的映射规则
     * @param sources 源对象列表
     * @param <T> 目标对象类型
     * @return 目标对象
     */
    @SafeVarargs
    public static <T, U> T copyProperties(T target, Function<String, String> keyMapping, U... sources) {
        return copyPropertiesTo(target, (prop, object) -> Optional.of(Prop.of(object, keyMapping.apply(prop.name()))), sources);
    }

    /**
     * <b>复制属性，并支持自定义从sources属性名映射到target属性名的映射规则</b>
     * @param target 期望copy到的目标对象
     * @param copyPropMapper 自定义从sources属性名映射到target属性名的映射规则
     * @param sources 源对象
     * @param <T> 目标对象类型
     * @return target对象
     */
    @SafeVarargs
    public static <T, U> T copyPropertiesTo(T target, BiFunction<Prop<Object, U>, T, Optional<Prop<Object, T>>> copyPropMapper, U... sources) {
        for (U source : sources) {
            //源对象上的所有可读属性
            Props.ofReadable(source).filter(Props::isNotGetClassProp).forEach(prop -> {
                //在对应可读属性的基础上查找一个期望赋值到target上的属性
                Optional<Prop<Object, T>> property = copyPropMapper.apply(prop, target);
                property.ifPresent(p -> p.set(prop.val()));
            });
        }
        return target;
    }

    /**
     * <b>将Map中的字段映射到实体类的属性中去</b>
     * <pre>{@code
     *  UserDto userDto = new UserDto().setId(12).setName("zhangSan");
     *  User user = new User();
     *  user.from(userDto);
     *  //User(id=12,name="zhangSan")
     *  System.out.println(user);
     * }</pre>
     * @param target 实体
     * @param source 源实体
     * @return 实体
     * @param <T> 实体类型
     */
    public static <T> T from(T target, Object source) {
        if ( target != null && source != null )
            Props.ofWritable(target).forEach(prop -> prop.tryGet(source));
        return target;
    }

}
